/*
    This source file is part of GeNa project.
    Copyright © GeNa project 2008
    For the latest info, see http://code.google.com/p/genaproject/ or  http://lumpyproject.forums-rpg.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. */

#ifndef WINDOW_MANAGER_H_INCLUDED
#define WINDOW_MANAGER_H_INCLUDED

#include <stack>


#ifndef WX_PCH_H_INCLUDED
#include "wx_pch.h"
#endif

#include <wx/splitter.h>
#include <wx/xml/xml.h>

#include <boost/archive/basic_archive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/nvp.hpp>

#include "managed_window.h"
#include "windows_factory.h"
#include "wx_serialization_helper.h"

using namespace boost::serialization;

class WindowsManager
{
public:
    enum
    {
        MODE_LEFT,
        MODE_RIGHT,
        MODE_BOTTOM,
        MODE_TOP
    };

    enum
    {
        MODE_HORIZONTAL,
        MODE_VERTICAL
    };

private:
    class Leaf;
    class Splitter;

    class Node
    {
    public:
        Splitter* splitter;
        Node* parent;

        Node* leftNode, *rightNode;
        Leaf* leftLeaf;

        union
        {
            Leaf* rightLeaf;
            wxWindow* parentWnd; //For root node
        };

        int mode;

        Node()
        {
            splitter = NULL;
            parent = NULL;
            leftNode = NULL;
            rightNode = NULL;
            leftLeaf = NULL;
            rightLeaf = NULL;
            mode = MODE_VERTICAL;
        }

        ~Node()
        {
            if( leftNode)
                delete leftNode;
            if( leftLeaf)
                delete leftLeaf;
            if( splitter)
            {
                if( rightNode)
                    delete rightNode;
                if( rightLeaf)
                    delete rightLeaf;
            }
        }

        template<class Archive> void serialize(Archive & ar, const unsigned int version);

        void updateLink();
        void updateSize()
        {
            if( splitter)
                splitter->UpdateSize();
            if( leftNode)
                leftNode->updateSize();
            if( rightNode)
                rightNode->updateSize();
        }

        wxSize getMinSize()
        {
            wxSize size1, size2;

            if( !splitter)
            {
                if( leftLeaf)
                    size1 = leftLeaf->wnd->getWindow()->GetMinSize();
                else if( leftNode)
                    size1 = leftNode->getMinSize();

                size1.IncTo(wxSize(20,20));
                return size1;
            }

            if( leftLeaf)
            {
                if( leftLeaf->wnd)
                    size1 = leftLeaf->wnd->getWindow()->GetMinSize();

                size1.IncTo(wxSize(20,20));
            }
            else
                size1 = leftNode->getMinSize();

            if( rightLeaf)
            {
                if( rightLeaf->wnd)
                    size2 = rightLeaf->wnd->getWindow()->GetMinSize();

                size2.IncTo(wxSize(20,20));
            }
            else
                size2 = rightNode->getMinSize();

            if( splitter->GetSplitMode() == wxSPLIT_HORIZONTAL)
            {
                if( size1.x < size2.x)
                    size1.x = size2.x;
                size1.y += size2.y + 5;
            }
            else
            {
                if( size1.y < size2.y)
                    size1.y = size2.y;
                size1.x += size2.x + 5;
            }

            return size1;
        }

        int getMinChange(int mode)
        {
            int min;

            if( splitter->GetSplitMode() == wxSPLIT_HORIZONTAL)
            {
                if( mode == MODE_LEFT || mode == MODE_RIGHT)
                {
                    if( leftNode)
                        min = leftNode->getMinChange(mode);
                    else if( leftLeaf->wnd)
                        min = leftLeaf->wnd->getWindow()->GetMinSize().x;
                    min = (min < 20) ? 20 : min;

                    int a;
                    if( rightNode)
                        a = rightNode->getMinChange(mode);
                    else if( rightLeaf->wnd)
                        a = rightLeaf->wnd->getWindow()->GetMinSize().x;
                    a = (a < 20) ? 20 : a;

                    if( a > min)
                        min = a;
                }
                else
                {
                    if( mode == MODE_TOP)
                    {
                        if( leftNode)
                            min = leftNode->getMinChange(mode);
                        else if( leftLeaf->wnd)
                            min = leftLeaf->wnd->getWindow()->GetMinSize().y;
                        if( min < 20)
                            min = 20;

                        if( rightNode)
                            min += rightNode->splitter->GetSize().y;
                        else if( rightLeaf->wnd)
                        {
                            int a = rightLeaf->wnd->getWindow()->GetSize().y;
                            min += (a < 20) ? 20 : a;
                        }
                        else
                            min += 20;
                    }
                    else
                    {
                        if( rightNode)
                            min = rightNode->getMinChange(mode);
                        else if( rightLeaf->wnd)
                            min = rightLeaf->wnd->getWindow()->GetMinSize().y;
                        if( min < 20)
                            min = 20;

                        if( leftNode)
                            min += leftNode->splitter->GetSize().y;
                        else if( leftLeaf->wnd)
                        {
                            int a = leftLeaf->wnd->getWindow()->GetSize().y;
                            min += (a < 20) ? 20 : a;
                        }
                        else
                            min += 20;
                    }
                }
            }
            else
            {
                if( mode == MODE_TOP || mode == MODE_BOTTOM)
                {
                    if( leftNode)
                        min = leftNode->getMinChange(mode);
                    else if( leftLeaf->wnd)
                        min = leftLeaf->wnd->getWindow()->GetMinSize().y;
                    if( min < 20)
                        min = 20;

                    int a;
                    if( rightNode)
                        a = rightNode->getMinChange(mode);
                    else if( rightLeaf->wnd)
                        a = rightLeaf->wnd->getWindow()->GetMinSize().y;
                    if( a < 20)
                        a = 20;

                    if( a > min)
                        min = a;
                }
                else
                {
                    if( mode == MODE_LEFT)
                    {
                        if( leftNode)
                            min = leftNode->getMinChange(mode);
                        else if( leftLeaf->wnd)
                            min = leftLeaf->wnd->getWindow()->GetMinSize().x;
                        if( min < 20)
                            min = 20;

                        if( rightNode)
                            min += rightNode->splitter->GetSize().x;
                        else if( rightLeaf->wnd)
                        {
                            int a = rightLeaf->wnd->getWindow()->GetSize().x;
                            min += (a < 20) ? 20 : a;
                        }
                        else
                            min += 20;
                    }
                    else
                    {
                        if( rightNode)
                            min = rightNode->getMinChange(mode);
                        else if( rightLeaf->wnd)
                            min = rightLeaf->wnd->getWindow()->GetMinSize().x;
                        if( min < 20)
                            min = 20;

                        if( leftNode)
                            min += leftNode->splitter->GetSize().x;
                        else if( leftLeaf->wnd)
                        {
                            int a = leftLeaf->wnd->getWindow()->GetSize().x;
                            min += (a < 20) ? 20 : a;
                        }
                        else
                            min += 20;
                    }
                }
            }

            return min + 5;
        }

        void prepareResizing(int mode)
        {
            if(splitter)
            {
                if( mode == MODE_LEFT || mode == MODE_TOP)
                    splitter->SetSashGravity(1.0f);
                else
                    splitter->SetSashGravity(0.0f);
            }

            if( leftNode)
                leftNode->prepareResizing(mode);
            if( rightNode)
                rightNode->prepareResizing(mode);
        }

        void prepareEquitableResizing()
        {
            if( splitter)
            {
                splitter->SetSashGravity(0.5f);
/*
                if( splitter->GetSplitMode() == wxSPLIT_VERTICAL)
                {
                    if( leftNode && (leftNode->getMinSize().x >= leftNode->splitter->GetSize().x))
                        splitter->SetSashGravity(0.0f);
                    else if( leftLeaf)
                    {
                        int size = leftLeaf->wnd->getWindow()->GetMinSize().x;
                        size = (size<20) ? 20 : size;
                        if( size >= leftLeaf->wnd->getWindow()->GetSize().x)
                            splitter->SetSashGravity(0.0f);
                    }
                    else if( rightNode && (rightNode->getMinSize().x >= rightNode->splitter->GetSize().x))
                        splitter->SetSashGravity(1.0f);
                    else if( rightLeaf)
                    {
                        int size = rightLeaf->wnd->getWindow()->GetMinSize().x;
                        size = (size<20) ? 20 : size;
                        if( size >= rightLeaf->wnd->getWindow()->GetSize().x)
                            splitter->SetSashGravity(1.0f);
                    }
                }
                else
                {
                    if( leftNode && (leftNode->getMinSize().y >= leftNode->splitter->GetSize().y))
                        splitter->SetSashGravity(0.0f);
                    else if( leftLeaf)
                    {
                        int size = leftLeaf->wnd->getWindow()->GetMinSize().y;
                        size = (size<20) ? 20 : size;
                        if( size >= leftLeaf->wnd->getWindow()->GetSize().y)
                            splitter->SetSashGravity(0.0f);
                    }
                    else if( rightNode && (rightNode->getMinSize().x >= rightNode->splitter->GetSize().y))
                        splitter->SetSashGravity(1.0f);
                    else if( rightLeaf)
                    {
                        int size = rightLeaf->wnd->getWindow()->GetMinSize().y;
                        size = (size<20) ? 20 : size;
                        if( size >= rightLeaf->wnd->getWindow()->GetSize().y)
                            splitter->SetSashGravity(1.0f);
                    }
                }*/
            }

            if( leftNode)
                leftNode->prepareEquitableResizing();
            if( rightNode)
                rightNode->prepareEquitableResizing();
        }

    };

    class Leaf
    {
    public:
        Node* parent;
        ManagedWindow* wnd;

        Leaf()
        {
            parent = NULL;
            wnd = NULL;
        }

        template<class Archive> void serialize(Archive & ar, const unsigned int version);
    };

    class Splitter : public wxSplitterWindow
    {
    protected:
        bool OnSashPositionChange(int pos)
        {
            if(!mNode)
                return true;

            int mode;
            mode = (GetSplitMode() == wxSPLIT_HORIZONTAL) ? MODE_BOTTOM : MODE_RIGHT;
            int min = 0;
            if( mNode->leftNode)
                min = mNode->leftNode->getMinChange(mode);
            else if(mNode->leftLeaf->wnd)
            {
                if( GetSplitMode() == wxSPLIT_HORIZONTAL)
                    min = mNode->leftLeaf->wnd->getWindow()->GetMinSize().y;
                else
                    min = mNode->leftLeaf->wnd->getWindow()->GetMinSize().x;
            }

            if(min < 20)
                min = 20;

            if( pos < min)
                return false;

            mode = (GetSplitMode() == wxSPLIT_HORIZONTAL) ? MODE_TOP : MODE_LEFT;
            int max = 0;
            if( mNode->rightNode)
                max = mNode->rightNode->getMinChange(mode);
            else if(mNode->rightLeaf->wnd)
            {
                if( GetSplitMode() == wxSPLIT_HORIZONTAL)
                    max = mNode->rightLeaf->wnd->getWindow()->GetMinSize().y;
                else
                    max = mNode->rightLeaf->wnd->getWindow()->GetMinSize().x;
            }

            if(max < 20)
                max = 20;

            if(GetSplitMode() == wxSPLIT_HORIZONTAL)
                max = GetSize().y - max;
            else
                max = GetSize().x - max;

            if( pos > max)
                return false;

            if( GetSplitMode() == wxSPLIT_HORIZONTAL)
            {
                if( mNode->leftNode)
                    mNode->leftNode->prepareResizing(MODE_RIGHT);
                if( mNode->rightNode)
                    mNode->rightNode->prepareResizing(MODE_LEFT);
            }
            else
            {
                if( mNode->leftNode)
                    mNode->leftNode->prepareResizing(MODE_BOTTOM);
                if( mNode->rightNode)
                    mNode->rightNode->prepareResizing(MODE_TOP);
            }

            return true;
        }

    public:
        Node* mNode;

        Splitter(wxWindow* parent) : wxSplitterWindow(parent, wxID_DEFAULT, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE)
        {
            SetSashGravity(0.5);
        }
    };


    Node* mRoot; //Special node, shall not be overwritten
    wxWindow* mParent;

    bool mSplitMode;
    Splitter* mSplitter;
    wxPoint mSplitterPoint;

    //Find window
    Leaf* getLeaf(wxPoint &p);
    Node* getNode(wxPoint &p, int depth);
    void getWindow(wxPoint &p, int depth, Node* &node, Leaf* &leaf);

    //Splitter control
    void setSplitterPosition(wxPoint p);
    void split();

    //Helper
    void updateLink(Node* node);


public :
    //Handler
    class ParentWindowEventHandler : public wxEvtHandler
    {
    private:
        WindowsManager* mMgr;
    protected:
        void onSize(wxSizeEvent &event)
        {
            for ( wxWindowList::compatibility_iterator node = mMgr->mParent->GetChildren().GetFirst();
              node;
              node = node->GetNext() )
            {
                wxWindow *win = node->GetData();
                win->SetSize(mMgr->mParent->GetSize());
            }

//            mMgr->mRoot->prepareEquitableResizing();
//            m_mgr->m_parent->SetMinSize( m_mgr->m_root->getMinSize() + m_mgr->m_parent->GetSize() - m_mgr->m_parent->GetClientSize());
//            wxEvtHandler* next = GetNextHandler();
//            if(next)
//                next->ProcessEvent(event);
        }

    public:
        ParentWindowEventHandler( WindowsManager* mgr)
        {
            mMgr = mgr;
        }

        DECLARE_EVENT_TABLE()
    };
    ParentWindowEventHandler ParentWndEvtH;

    void onMouseMove(wxMouseEvent &event);
    void onMouseRightClick(wxMouseEvent &event);
    void onMouseLeftClick(wxMouseEvent &event);
    void onKeyUp(wxKeyEvent &event);
    void onFocusEvent(wxFocusEvent &event);

public :

    WindowsManager(wxWindow* parent);
    ~WindowsManager();

    wxWindow* getParent()
    {
        return mParent;
    }

    //Add window
    void addWindow(ManagedWindow* wnd, wxPoint p, int mode);

    void removeWindow(ManagedWindow* wnd);

    //Load/Save layout
    bool readConfigurationFromFile(wxXmlDocument &file);
    bool readConfigurationFromFile(const wxString &file);

    void writeConfigurationToFile(wxXmlDocument &file);
    void writeConfigurationToFile(const wxString &file);

    class ReadConfCoroutine : public wxTimer
    {
    private:
        WindowsManager* mgr;
        wxXmlDocument file;

        std::stack<Node*> s;
        std::stack<wxSize> ss;
        std::stack<wxXmlNode*> xs;

        Splitter* waitingSplitter;

        void Notify()
        {
            Next();
        }

        void onSize(wxSizeEvent &event)
        {

            ///Start(50,true);
        }

    public:
        void start(WindowsManager* mgr, wxXmlDocument &_file);
        void Next();

        DECLARE_EVENT_TABLE()
    };

public :

    template<class Archive> void serialize(Archive & ar, const unsigned int version)
    {
        ar & make_nvp("root",*mRoot);
    }
};



#endif // WINDOW_MANAGER_H_INCLUDED
